package com.quant.background.modular.base.login.service.impl;

import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.PhoneUtil;
import cn.hutool.core.util.StrUtil;
import com.quant.background.api.SaBaseLoginUserApi;
import com.quant.background.core.pojo.SaBaseLoginUser;
import com.quant.background.core.util.StpLoginUserUtil;
import com.quant.background.modular.base.login.enums.AuthDeviceTypeEnum;
import com.quant.background.modular.base.login.enums.AuthExceptionEnum;
import com.quant.background.modular.base.login.param.AuthAccountPasswordLoginParam;
import com.quant.background.modular.base.login.service.AuthService;
import com.quant.background.modular.base.message.cache.UnreadMessageCountCacheKey;
import com.quant.background.modular.base.message.service.DevMessageService;
import com.quant.common.cache.CommonCacheOperator;
import com.quant.common.exception.CommonException;
import com.quant.common.util.CommonCryptogramUtil;
import com.quant.common.util.CommonEmailUtil;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;

/**
 * 登录Service接口实现类
 *
 * @author xuyuxiang
 * @date 2021/12/23 21:52
 */
@Service
public class AuthServiceImpl implements AuthService {

    private static final String SNOWY_SYS_DEFAULT_CAPTCHA_OPEN_KEY = "SNOWY_SYS_DEFAULT_CAPTCHA_OPEN";

    private static final String AUTH_VALID_CODE_CACHE_KEY = "auth-validCode:";

    private static final String LOGIN_ERROR_TIMES_KEY_PREFIX = "login-error-times:";

    @Resource(name = "loginUserApi")
    private SaBaseLoginUserApi loginUserApi;

    @Resource
    private DevMessageService messageService;

    @Resource
    private CommonCacheOperator commonCacheOperator;

    /**
     * 校验验证码方法
     *
     * @author xuyuxiang
     * @date 2022/8/25 15:26
     **/
    private void validValidCode(String phoneOrEmail, String validCode, String validCodeReqNo) {
        // 依据请求号，取出缓存中的验证码进行校验
        Object existValidCode;
        if (ObjectUtil.isEmpty(phoneOrEmail)) {
            existValidCode = commonCacheOperator.get(AUTH_VALID_CODE_CACHE_KEY + validCodeReqNo);
        } else {
            existValidCode = commonCacheOperator.get(AUTH_VALID_CODE_CACHE_KEY + phoneOrEmail + StrUtil.UNDERLINE + validCodeReqNo);
        }
        // 为空则直接验证码错误
        if (ObjectUtil.isEmpty(existValidCode)) {
            throw new CommonException(AuthExceptionEnum.VALID_CODE_ERROR.getValue());
        }
        // 移除该验证码
        if (ObjectUtil.isEmpty(phoneOrEmail)) {
            commonCacheOperator.remove(AUTH_VALID_CODE_CACHE_KEY + validCodeReqNo);
        } else {
            commonCacheOperator.remove(AUTH_VALID_CODE_CACHE_KEY + phoneOrEmail + StrUtil.UNDERLINE + validCodeReqNo);
        }
        // 不一致则直接验证码错误
        if (!validCode.equals(Convert.toStr(existValidCode).toLowerCase())) {
            throw new CommonException("验证码错误");
        }
    }

    /**
     * 校验手机号与验证码等参数
     *
     * @author xuyuxiang
     * @date 2022/8/25 14:29
     **/
    private void validPhoneValidCodeParam(String phoneOrEmail, String validCode, String validCodeReqNo, String type) {
        // 验证码正确则校验手机号格式
        if (ObjectUtil.isEmpty(phoneOrEmail)) {
            // 执行校验验证码
            validValidCode(null, validCode, validCodeReqNo);
        } else {
            if (!PhoneUtil.isMobile(phoneOrEmail) && !CommonEmailUtil.isEmail(phoneOrEmail)) {
                throw new CommonException(AuthExceptionEnum.PHONE_FORMAT_ERROR.getValue());
            }
            // 执行校验验证码
            validValidCode(phoneOrEmail, validCode, validCodeReqNo);

            if (ObjectUtil.isEmpty(loginUserApi.getUserByPhone(phoneOrEmail))) {
                throw new CommonException(AuthExceptionEnum.PHONE_ERROR.getValue());
            }
        }
    }

    @Override
    public String doLogin(AuthAccountPasswordLoginParam param, String type) {
        // 判断账号是否被封禁
        isDisableTime(param.getAccount());
        // 获取账号
        String account = param.getAccount();
        // 获取密码
        String password = param.getPassword();
        // 获取设备
        String device = param.getDevice();
        // 默认指定为PC，如在小程序跟移动端的情况下，自行指定即可
        if (ObjectUtil.isEmpty(device)) {
            device = AuthDeviceTypeEnum.PC.getValue();
        } else {
            AuthDeviceTypeEnum.validate(device);
        }

        SaBaseLoginUser saBaseLoginUser = loginUserApi.getUserByAccount(account);

        // SM2解密并获得前端传来的密码哈希值
        String passwordHash;
        try {
            // 解密，并做哈希值
            passwordHash = CommonCryptogramUtil.doHashValue(CommonCryptogramUtil.doSm2Decrypt(password));
        } catch (Exception e) {
            throw new CommonException(AuthExceptionEnum.PWD_DECRYPT_ERROR.getValue());
        }
        if (ObjectUtil.isEmpty(saBaseLoginUser)) {
            throw new CommonException(AuthExceptionEnum.ACCOUNT_ERROR.getValue());
        }
        if (!saBaseLoginUser.getPassword().equals(passwordHash)) {
            // 记录登录次数 和 过期时间
            saveLoginTimes(account);
            throw new CommonException(AuthExceptionEnum.PWD_ERROR.getValue());
        }
        // 删除redis 中的key
        clearLoginErrorTimes(account);
        // 执行B端登录
        return execLoginB(saBaseLoginUser, device);
    }

    /**
     * 是否封禁状态
     * 如果被封禁了，执行以下逻辑，返回前端还需等待的时间
     */
    private void isDisableTime(String userAccount) {
        // disableTime = -2表示未被封禁
        long disableTime = StpUtil.getDisableTime(userAccount);
        if (disableTime > 0) {
            if (disableTime > 60) {
                throw new CommonException(userAccount + "账号已被封禁, 请再" + disableTime / 60 + "分钟后重新尝试登录!!");
            }
            throw new CommonException(userAccount + "账号已被封禁, 请再" + disableTime + "秒后重新尝试登录!!");
        }
    }

    // redis中保存登录错误次数
    private void saveLoginTimes(String userAccount) {
        String loginErrorKey = LOGIN_ERROR_TIMES_KEY_PREFIX + userAccount;
        Integer number = (Integer) commonCacheOperator.get(loginErrorKey);
        if (number == null) {
            // 如果redis中没有保存，代表失败第一次
            number = 2;
            commonCacheOperator.put(loginErrorKey, number, 5 * 60);
            return;
        }
        if (number < 5) {
            number++;
            commonCacheOperator.put(loginErrorKey, number, 5 * 60);
            return;
        }
        // 第五次封禁账号,第六次进入isDisableTime方法，返回用户还需等待时间
        StpUtil.disable(userAccount, 5 * 60);
        // 删除redis 中的key
        clearLoginErrorTimes(userAccount);

    }

    /**
     * 登录成功、清空登录次数
     *
     * @param userAccount 账号
     */
    private void clearLoginErrorTimes(String userAccount) {
        String loginErrorKey = LOGIN_ERROR_TIMES_KEY_PREFIX + userAccount;
        // 删除redis中的key
        commonCacheOperator.remove(loginErrorKey);
    }

    /**
     * 执行B端登录
     **/
    private String execLoginB(SaBaseLoginUser saBaseLoginUser, String device) {
        // 校验状态
        if (!saBaseLoginUser.getEnabled()) {
            throw new CommonException(AuthExceptionEnum.ACCOUNT_DISABLED.getValue());
        }
        // 执行登录
        StpUtil.login(saBaseLoginUser.getId(), new SaLoginModel().setDevice(device).setExtra("name", saBaseLoginUser.getName()));
//        // 角色集合
//        List<JSONObject> roleList = loginUserApi.getRoleListByUserId(saBaseLoginUser.getId());
//        // 角色id集合
//        List<Long> roleIdList = roleList.stream().map(jsonObject -> jsonObject.getLong("id")).collect(Collectors.toList());
//        // 角色码集合
//        List<String> roleCodeList = roleList.stream().map(jsonObject -> jsonObject.getStr("code")).collect(Collectors.toList());
//        // 角色id和用户id集合
//        List<Long> userAndRoleIdList = CollectionUtil.unionAll(roleIdList, CollectionUtil.newArrayList(saBaseLoginUser.getId()));
//        // 获取按钮码
//        saBaseLoginUser.setButtonCodeList(loginUserApi.getButtonCodeListListByUserAndRoleIdList(userAndRoleIdList));
//        // 获取权限码
//        saBaseLoginUser.setPermissionCodeList(saBaseLoginUser.getDataScopeList().stream()
//                .map(SaBaseLoginUser.DataScope::getApiUrl).collect(Collectors.toList()));
//        // 获取角色码
//        saBaseLoginUser.setRoleCodeList(roleCodeList);

        // 获取未读消息数量
        Long unreadCount = messageService.unreadCount(String.valueOf(saBaseLoginUser.getId()));
        commonCacheOperator.mapPut(UnreadMessageCountCacheKey.unreadMessageCountCacheKey, String.valueOf(saBaseLoginUser.getId()), unreadCount);

        // 缓存用户信息，此处使用TokenSession为了指定时间内无操作则自动下线
        StpUtil.getTokenSession().set("loginUser", saBaseLoginUser);
        // 返回token
        return StpUtil.getTokenInfo().tokenValue;
    }

    /**
     * 获取B端登录用户信息
     **/
    @Override
    public SaBaseLoginUser getLoginUser() {
        SaBaseLoginUser saBaseLoginUser = StpLoginUserUtil.getLoginUser();
        saBaseLoginUser.setPassword(null);
        saBaseLoginUser.setPermissionCodeList(null);
        saBaseLoginUser.setDataScopeList(null);
        return saBaseLoginUser;
    }

}
